package com.ezlcp.user.controller;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson2.JSONObject;
import com.ezlcp.commons.base.db.BaseService;
import com.ezlcp.commons.base.entity.JsonResult;
import com.ezlcp.commons.constant.Constants;
import com.ezlcp.commons.constant.StatusEnum;
import com.ezlcp.commons.dto.LoginLog;
import com.ezlcp.commons.model.SysUser;
import com.ezlcp.commons.tool.DateUtils;
import com.ezlcp.commons.tool.Encrypt;
import com.ezlcp.commons.tool.StringUtils;
import com.ezlcp.commons.utils.ContextUtil;
import com.ezlcp.commons.utils.GoogleAuthenticator;
import com.ezlcp.commons.utils.RequestUtil;
import com.ezlcp.commons.utils.TokenUtil;
import com.ezlcp.user.entity.Company;
import com.ezlcp.user.entity.Settings;
import com.ezlcp.user.entity.User;
import com.ezlcp.user.enums.ClientTypeEnum;
import com.ezlcp.user.enums.IpStrategyEnum;
import com.ezlcp.user.service.*;
import com.ezlcp.user.web.BaseController;
import com.wf.captcha.GifCaptcha;
import com.wf.captcha.base.Captcha;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.*;

import java.util.Date;

/**
 * @author Elwin ZHANG
 * @description: 权限认证的控制器<br />
 * @date 2022/5/6 16:18
 */
@Tag(name = "登录、登出接口")
@Slf4j
@RestController
@RequestMapping("/ezlcp/user/auth")
public class AuthController extends BaseController {
    public static final String LOCAL_IP = "localhost";
    @Resource
    private ValidateCodeServiceImpl validateCodeService;
    @Resource
    private UserServiceImpl userService;
    @Resource
    private CompanyServiceImpl companyService;
    @Resource
    private IpStrategyServiceImpl ipStrategyService;
    @Resource
    private SettingsServiceImpl settingsService;
    @Resource
    private LogService logService;

    /***
     * @description: 是否使用图形验证码
     */
    @Value("${use-captcha:true}")
    private boolean isUseCaptcha;

    @Operation(summary = "a.获取图形验证码")
    @GetMapping("validate/code/{deviceId}")
    public void createCode(@PathVariable(name = "deviceId") String deviceId, HttpServletResponse response) throws Exception {
        Assert.notNull(deviceId, "机器码不能为空");
        // 设置请求头为输出图片类型
        response.setContentType("image/gif");
        response.setHeader("Pragma", "No-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0L);
        response.setHeader("blob", "true");

        // 三个参数分别为宽、高、位数
        GifCaptcha gifCaptcha = new GifCaptcha(100, 35, 4);
        // 设置类型：字母数字混合
        gifCaptcha.setCharType(Captcha.TYPE_DEFAULT);
        // 保存验证码
        validateCodeService.saveImageCode(deviceId, gifCaptcha.text().toLowerCase());
        // 输出图片流
        gifCaptcha.out(response.getOutputStream());
    }


    @Operation(summary = "b.登录", description = "如果公司配置需要google验证，则先返回google二维码地址")
    @PostMapping("/login")
    public JsonResult login(@Parameter(description = "用户名") @RequestParam(value = "userName") String userName,
                            @Parameter(description = "登录密码") @RequestParam(value = "password") String password,
                            @Parameter(description = "图形验证码") @RequestParam(value = "validCode", required = false) String validCode,
                            @Parameter(description = "设备ID") @RequestParam(value = "deviceId", required = false) String deviceId,
                            @Parameter(description = "客户端类型，默认为0。 0.PC 1.H5 2.安卓 4.IOS") @RequestParam(value = "clientType", required = false) Integer clientType,
                            HttpServletRequest request) {
        if (StringUtils.isEmpty(userName) || StringUtils.isEmpty(password)) {
            return JsonResult.Fail("auth.userOrPasswordNull");
        }
        String ipAddr = RequestUtil.getIpAddress();
        if (clientType == null) {
            clientType = ClientTypeEnum.PC.getValue();
        }
        LoginLog log = initLoginLog(userName, ipAddr, clientType);
        // 需要验证图形验证码
        if (isUseCaptcha) {
            try {
                validateCodeService.validate(request);
            } catch (Exception e) {
                writeLoginFailLog(log, "auth.incorrectVerificationCode");
                return JsonResult.Fail("auth.incorrectVerificationCode");
            }
        }
        // 通过用户查找用户
        User user = userService.getByUserName(userName);
        //检查用户状态
        String checkResult = checkUserStatus(user, log);
        if (checkResult.length() > 0) {
            return JsonResult.Fail(checkResult);
        }
        String tenantId = user.getTenantId();
        log.setUserId(user.getUserId());
        log.setTenantId(tenantId);
        //获取配置信息
        Settings settings = settingsService.selectByTenantId(tenantId);
        //检查密码，是否锁定以及IP策略
        checkResult = checkPasswordAndIp(user, password, ipAddr, settings, log);
        if (checkResult.length() > 0) {
            return JsonResult.Fail(checkResult);
        }
        String logo = null;
        Company company = companyService.getById(tenantId);
        if (ObjectUtil.isNotNull(company)) {
            logo = company.getUrl();
        }
        //判断是否授权过期
        if (user.isTenantUser() && company != null) {
            Date timeLimit = DateUtils.addDays(company.getTimeLimit(), 1);
            if (timeLimit.before(new Date())) {
                return JsonResult.Fail("auth.accountOverdue");
            }
        }
        String secretKey = user.getSecretKey();
        // 判断Google密钥是否为空，为空自动生成
        if (ObjectUtil.isEmpty(secretKey)) {
            user.setSecretKey(GoogleAuthenticator.generateSecretKey());
        }
        if (user.isAdmin() && user.isTenantUser() && company != null) {
            user.setName(company.getName());
        }
        // 转换用户
        SysUser sysUser = userService.convert2SysUser(user);
        sysUser.setIpAddr(ipAddr);
        sysUser.setUserAgent(request.getHeader("User-Agent"));
        sysUser.setClientType(clientType);
        sysUser.setCacheSeconds(settingsService.getIdleMinutes(settings));
        // 更新用户信息并插入登录日志
        user.setIsLock(Constants.SHORT0);
        user.setLockTime(null);
        user.setLoginFailTimes(Constants.SHORT0);
        user.setSeq(user.getSeq() + 1);
        userService.updateById(user);
        logService.saveLoginLog(log);
        //产生token
        String token;
        JSONObject data = new JSONObject();
        if (user.isPlatformUser()) {
            // 判断是否提示绑定验证码
            if (settings != null && settings.getGoogleAuth() == 1) {
                // 需要提示，返回QRCode
                data.put("QRBarcodeURL", GoogleAuthenticator.getQRBarcodeURL(user.getName(), secretKey));
                data.put("isBind", user.getIsBind());
            }
            token = TokenUtil.dealOtherIpLogin(sysUser);
        } else {
            // 不需要提示，直接登录获取token
            token = TokenUtil.newTokenAndCache(sysUser);
        }
        data.put("token", token);
        data.put("user", sysUser);
        data.put("logo", logo);
        return JsonResult.getSuccessResult(data, "auth.loginSuccess");
    }

    /***
     * @description 检查用户输入的密码，是否锁定，
     * @param user 用户对象
     * @param password 前端传回的密码
     * @param ipAddr 登录IP地址
     * @param settings 公司设置
     * @param log 登录日志对象
     * @return java.lang.String
     * @author Elwin ZHANG
     * @date 2023/1/30 14:09
     */
    private String checkPasswordAndIp(User user, String password, String ipAddr, Settings settings, LoginLog log) {
        String userId = user.getUserId();
        String encPw = Encrypt.getMd5Password(userId, password);
        String tenantId = user.getTenantId();

        // 是否锁定
        if (user.getIsLock() == 1 && !user.isSuperAdmin()) {
            //判断锁定时间是否已经失效
            int lockMinutes = settingsService.getLockMinutes(settings);
            if (lockMinutes > 0 && !isLockTimeOverdue(user.getLockTime(), lockMinutes)) {
                writeLoginFailLog(log, "auth.lockUser");
                return "auth.lockUser";
            }
        }
        int ipStrategy = settingsService.getIpStrategy(settings);
        //黑名单控制
        if (ipStrategy == IpStrategyEnum.BlackList.getValue() && !LOCAL_IP.equals(ipAddr) && !user.isAdmin()) {
            var list = ipStrategyService.selectByTenantAndType(tenantId, IpStrategyEnum.BlackList.getValue());
            if (list.contains(ipAddr)) {
                writeLoginFailLog(log, "auth.isIPNotLogin");
                return "auth.isIPNotLogin";
            }
        }
        //白名单控制
        if (ipStrategy == IpStrategyEnum.WhiteList.getValue() && !LOCAL_IP.equals(ipAddr) && !user.isAdmin()) {
            var list = ipStrategyService.selectByTenantAndType(tenantId, IpStrategyEnum.WhiteList.getValue());
            if (!list.contains(ipAddr)) {
                writeLoginFailLog(log, "auth.isIPNotLogin");
                return "auth.isIPNotLogin";
            }
        }
        // 校验密码
        if (!encPw.equals(user.getPassword())) {
            //失败次数+1
            short failTimes = user.getLoginFailTimes() == null ? 0 : user.getLoginFailTimes();
            failTimes += 1;
            user.setLoginFailTimes(failTimes);
            int maxFailTimes = settingsService.getMaxLoginFailTimes(settings);
            //超出最多失败次数，锁定账号
            if (maxFailTimes > 0 && failTimes >= maxFailTimes) {
                user.setIsLock(Constants.SHORT1);
                user.setLockTime(new Date());
            }
            user.setSeq(user.getSeq() + 1);
            userService.updateById(user);
            writeLoginFailLog(log, "auth.noUserOrPassword");
            return "auth.noUserOrPassword";
        }
        return "";
    }

    /***
     * @description 校验用户状态
     * @param user 用户对象
     * @param loginLog 登录日志对象
     * @return java.lang.String  正常就返回空字符，否则返回错误信息
     * @author Elwin ZHANG
     * @date 2023/1/30 13:54
     */
    private String checkUserStatus(User user, LoginLog loginLog) {
        // 用户是否为空
        if (user == null) {
            writeLoginFailLog(loginLog, "auth.noUserOrPassword");
            return "auth.noUserOrPassword";
        }
        String status = user.getStatus();
        if (status.equals(StatusEnum.deleted.name()) || status.equals(StatusEnum.disable.name())) {
            writeLoginFailLog(loginLog, "auth.isUserNotLogin");
            return "auth.isUserNotLogin";
        }
        return "";
    }

    /***
     * @description 初始化登录显示屏对象
     * @param userName 登录账号
     * @param ipAddr Ip地址
     * @param clientType 客户端类型
     * @return com.ezlcp.commons.dto.LoginLog
     * @author Elwin ZHANG
     * @date 2023/1/30 11:52
     */
    private LoginLog initLoginLog(String userName, String ipAddr, Integer clientType) {
        LoginLog log = new LoginLog();
        log.setIsFail(Constants.SHORT0);
        log.setLoginName(userName);
        log.setLoginTime(new Date());
        log.setIpAddr(ipAddr);
        log.setClientType(getClientType(clientType));
        return log;
    }

    /***
     * @description 锁定时长是否已经到达
     * @param lockTime 锁定开始时间
     * @param minutes 每次锁定时长
     * @return boolean
     * @author Elwin ZHANG
     * @date 2023/1/29 16:01
     */
    private boolean isLockTimeOverdue(Date lockTime, int minutes) {
        if (lockTime == null) {
            return true;
        }
        //锁定时间加上锁定时长是否已度过
        Date limitDate = DateUtils.addMinutes(lockTime, minutes);
        if (limitDate.before(new Date())) {
            return true;
        }
        return false;
    }

    /***
     * @description 获取客户端类型，NULL值改为0
     * @param clientType 客户端类型
     * @return int
     * @author Elwin ZHANG
     * @date 2023/1/31 11:30
     */
    private short getClientType(Integer clientType) {
        if (clientType == null) {
            return 0;
        }
        return clientType.shortValue();
    }

    @Operation(summary = "f.登出")
    @PostMapping("/logout")
    public JsonResult logout(@Parameter(description = "客户户端类型，默认为0。 0.PC 1.H5 2.安卓 4.IOS") @RequestParam(value = "clientType", required = false) Integer clientType,
                             HttpServletRequest request) {
        String token = request.getHeader(Constants.TOKEN);
        if (StringUtils.isEmpty(token)) {
            token = request.getHeader(Constants.Authorization);
        }
        short type = getClientType(clientType);
        if (StringUtils.isNotEmpty(token)) {
            logService.logOut(ContextUtil.getCurrentUserId(), type);
            TokenUtil.removeToken(token);
        }
        return JsonResult.getSuccessResult("auth.logOutSuccess");
    }


    @Operation(summary = "c.完成绑定Google验证并校验", description = "下载Google验证并扫描二维码后完成绑定接口")
    @PostMapping("/bind")
    public JsonResult bind(@Parameter(description = "用户名") @RequestParam(value = "userName") String userName,
                           @Parameter(description = "登录密码") @RequestParam(value = "password") String password,
                           @Parameter(description = "Google验证码") @RequestParam(value = "googleCode") Long googleCode,
                           @Parameter(description = "客户户端类型，默认为0。 0.PC 1.H5 2.安卓 4.IOS") @RequestParam(value = "clientType", required = false) Integer clientType) {
        if (StringUtils.isEmpty(userName) || StringUtils.isEmpty(password)) {
            return JsonResult.getFailResult("auth.noUserOrPassword");
        }
        if (clientType == null) {
            clientType = ClientTypeEnum.PC.getValue();
        }
        String ipAddr = RequestUtil.getIpAddress();
        LoginLog log = initLoginLog(userName, ipAddr, clientType);
        // 通过用户查找用户
        User user = userService.getByUserName(userName);
        //检查用户状态
        String checkResult = checkUserStatus(user, log);
        if (checkResult.length() > 0) {
            return JsonResult.Fail(checkResult);
        }
        // 判断Google验证码是否合法
        GoogleAuthenticator googleAuthenticator = new GoogleAuthenticator();
        boolean checkCode = googleAuthenticator.checkCode(user.getSecretKey(), googleCode, System.currentTimeMillis());
        if (!checkCode) {
            writeLoginFailLog(log, "auth.googleAuthCodeError");
            return JsonResult.getFailResult("auth.googleAuthCodeError");
        }
        String tenantId = user.getTenantId();
        log.setUserId(user.getUserId());
        log.setTenantId(tenantId);
        //获取配置信息
        Settings settings = settingsService.selectByTenantId(tenantId);
        //检查密码，是否锁定以及IP策略
        checkResult = checkPasswordAndIp(user, password, ipAddr, settings, log);
        if (checkResult.length() > 0) {
            return JsonResult.Fail(checkResult);
        }
        String logo = null;
        Company company = companyService.getById(tenantId);
        if (ObjectUtil.isNotNull(company)) {
            logo = company.getUrl();
        }
        //判断是否授权过期
        if (user.isTenantUser() && company != null) {
            Date timeLimit = DateUtils.addDays(company.getTimeLimit(), 1);
            if (timeLimit.before(new Date())) {
                return JsonResult.Fail("auth.accountOverdue");
            }
        }

        // 上述判断无误，则user的是否与Google绑定字段设置为已绑定
        user.setIsBind(Constants.SHORT1);
        user.setIsLock(Constants.SHORT0);
        user.setLockTime(null);
        user.setLoginFailTimes(Constants.SHORT0);
        user.setSeq(user.getSeq() + 1);
        userService.updateById(user);
        logService.saveLoginLog(log);
        // 转换用户
        if (user.isAdmin() && user.isTenantUser() && company != null) {
            user.setName(company.getName());
        }
        SysUser sysUser = userService.convert2SysUser(user);
        sysUser.setIpAddr(ipAddr);
        sysUser.setUserAgent(request.getHeader("User-Agent"));
        sysUser.setClientType(clientType);
        sysUser.setCacheSeconds(settingsService.getIdleMinutes(settings));

        String token;
        if (user.isPlatformUser()) {
            token = TokenUtil.newTokenAndCache(sysUser);
        } else {
            token = TokenUtil.dealOtherIpLogin(sysUser);
        }
        JSONObject data = new JSONObject();
        data.put("token", token);
        data.put("user", sysUser);
        data.put("logo", logo);
        return JsonResult.getSuccessResult(data, "auth.loginSuccess");
    }

    @Operation(summary = "d.获取Google验证二维码")
    @PostMapping("/getQRBarcodeURL")
    public JsonResult getQRBarcodeURL() {
        User user = userService.getById(ContextUtil.getCurrentUserId());
        if (ObjectUtil.isEmpty(user)) {
            logService.saveSystemLog(getComment(), "getQRBarcodeURL", null, "账号无效");
            return JsonResult.getFailResult("auth.userNullity");
        }
        JSONObject data = new JSONObject();
        data.put("QRBarcodeURL", GoogleAuthenticator.getQRBarcodeURL(user.getName(), user.getSecretKey()));
        data.put("isBind", user.getIsBind());
        return JsonResult.getSuccessResult(data);
    }

    @Operation(summary = "e.解除Google验证绑定")
    @PostMapping("/unbind")
    public JsonResult unbind(@Parameter(description = "Google验证码") @RequestParam(value = "googleCode") Long googleCode) {
        User user = userService.getById(ContextUtil.getCurrentUserId());
        if (ObjectUtil.isEmpty(user)) {
            logService.saveSystemLog(getComment(), "unbind", null, "账号无效");
            return JsonResult.getFailResult("auth.userNullity");
        }
        // 判断code是否合法
        GoogleAuthenticator googleAuthenticator = new GoogleAuthenticator();
        boolean checkCode = googleAuthenticator.checkCode(user.getSecretKey(), googleCode, System.currentTimeMillis());
        if (!checkCode) {
            logService.saveSystemLog(getComment(), "unbind", user.getUserId(), "Google验证码有误");
            return JsonResult.getFailResult("auth.googleAuthCodeError");
        }
        user.setIsBind(Constants.SHORT0);
        user.setSeq(user.getSeq() + 1);
        Boolean updateUser = userService.updateById(user);
        return updateUser ? JsonResult.getSuccessResult("auth.untieSuccess") : JsonResult.getFailResult("auth.untieFail");
    }

    /***
     * @description: 写入登录失败日志
     * @param log 日志对象
     * @param reason 失败原因
     * @author Elwin ZHANG
     * @date 2022/5/11 16:33
     */
    private void writeLoginFailLog(LoginLog log, String reason) {
        log.setIsFail(Constants.SHORT1);
        log.setFailReason(reason);
        logService.saveLoginLog(log);
    }

    @Override
    public BaseService getBaseService() {
        return null;
    }

    @Override
    public String getComment() {
        return "登录、登出接口";
    }
}